package javango.contrib.jquery;


import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.PersistentClass;

import com.google.inject.Inject;

import javango.contrib.hibernate.HibernateUtil;
import javango.contrib.hibernate.ModelForm;
import javango.contrib.hibernate.Pagination;
import javango.contrib.jquery.widgets.JqueryLookupWidget;
import javango.db.Manager;
import javango.db.Managers;
import javango.db.Paginator;
import javango.db.QuerySet;
import javango.db.Paginator.Page;
import javango.forms.Forms;
import javango.forms.fields.CharField;
import javango.forms.fields.Field;
import javango.forms.fields.FieldFactory;
import javango.core.User;
import javango.http.HttpRequest;
import javango.http.HttpResponse;
import javango.http.SimpleHttpResponse;
import static javango.contrib.freemarker.Helper.renderToResponse;

public class JqueryViews {

	HibernateUtil hibernateUtil;
	Managers managers;
	Forms forms;
	FieldFactory fieldFactory;

	@Inject
	public JqueryViews(HibernateUtil hibernateUtil, Managers managers, Forms forms, FieldFactory fields) {
		super();
		this.hibernateUtil = hibernateUtil;
		this.managers = managers;
		this.forms = forms;
		this.fieldFactory = fields;
	}


	protected String getSearchProperty(User u, PersistentClass pc) { // throws NotAuthorizedException {
		Class<?> modelClass = pc.getMappedClass();
		Promptable p = (Promptable)modelClass.getAnnotation(Promptable.class);
		if (p == null) return null;

		if ("".equals(p.requiredRole())) { // no authority required
			return p.property();
		}

		if (u == null) {
			//                throw new NotAuthorizedException();
			return null;
		}

		if ("*".equals(p.requiredRole())) { // any authorized user
			return p.property();
		}
		if (u.hasRole(p.requiredRole())) { // user has the role
			return p.property();
		}

		// throw new NotAuthorizedException();
		return null;
	}

	public HttpResponse lookup(HttpRequest request, String model) throws Exception {
		Configuration cfg = hibernateUtil.getConfiguration();	
		PersistentClass pc = cfg.getClassMapping(model);

		String queryString = request.getParameter("q");

		String searchProperty = getSearchProperty(request.getUser(), pc);
		if (searchProperty == null) {
			return new SimpleHttpResponse("Unsupported class " + model + "|").setMimeType("text/plain");
		}
		Manager<Object> manager = managers.forClass(pc.getMappedClass());
		Pagination p = new Pagination(new Long(100), null, null);  // TODO Make this configurable

		List<Object> l = manager.filter(searchProperty + "__ilike", queryString + "%" )
		.limit(0,100)
		.list();

		StringBuilder b = new StringBuilder();
		for (Object o : l) {
			b.append(String.format("%s|%s\n", o.toString(), manager.getPk(o)));
		}
		return new SimpleHttpResponse(b.toString()).setMimeType("text/plain");
	}


	public HttpResponse promptForm(HttpRequest request) throws Exception {
		return doPrompt(request, false);		
	}
	
	public HttpResponse prompt(HttpRequest request) throws Exception {
		return doPrompt(request, true);
	}
	
	public String asQueryString(Map<String, String[]> map) {
		StringBuilder s = new StringBuilder();
		for (Entry<String, String[]> e : map.entrySet()) {
			s.append(String.format("%s=%s&", e.getKey(), e.getValue()[0]));
		}
		return s.toString();
	}
	
	public HttpResponse doPrompt(HttpRequest request, boolean search) throws Exception {
		String encodedString = request.getParameter("prompt_data");
		Map<String, Object> context = new HashMap<String, Object>();
		
		Map<String, String> params = JqueryLookupWidget.decrypt(encodedString);
		Configuration cfg = hibernateUtil.getConfiguration();	
		PersistentClass pc = cfg.getClassMapping(params.get("model"));

		ModelForm form = forms.newForm(ModelForm.class);
		form.setPrefix("jquery_lookup_form"); // make sure field names don't conflict with something else in the form.
		Class<?> clazz = pc.getMappedClass();
		form.setModel(clazz);
		String fields = params.get("search");
		String fieldName = params.get("field");
		String display = params.get("display");		
		String results = params.get("results"); // URLDecoder.decode(params.get("results"), "UTF-16");
		String[] resultColumns = results.split(",");
				
		context.put("StringFormat12", new javango.util.StringFormat12()); // needed in the template to format the display
		context.put("field_name", fieldName);
		context.put("field_list", resultColumns);
		context.put("display", display);
		context.put("prompt_data", URLEncoder.encode(encodedString, "UTF-8"));
		
		// fields may have joins or header information,  parse
		List<String> modelFields = new ArrayList<String>(); // list of fields that come directly from the model
		Map<String,Field> addFormFields = new HashMap<String, Field>(); // map of form fields to add to the form
		for(String field : fields.split(",")) {
			field = field.trim();
			String[] parts = field.split(":");
			if (parts.length == 1) {
				modelFields.add(field);
			} else {  // this field has title information included.
				// Verbose Name:field__join__searchtype
				// Yes this means that in order for joins to be used the field must have a custom name TODO
				// parts[0] = verboseName
				// parts[1] = field
				
				Field formField = fieldFactory.newField(CharField.class)
									.setAllowNull(true)
									.setRequired(false)
									.setName(parts[1])
									.setVerboseName(parts[0]);
				addFormFields.put(parts[1],formField);
			}
		}
		if (!modelFields.isEmpty()) {
			form.setInclude(modelFields.toArray(new String[]{}));
		} else {
			form.setInclude("");
		}
		form.getFields().putAll(addFormFields);

		for(Field f : form.getFields().values()) {
			f.setRequired(false).setAllowNull(true);
		}

		if (search) {
			context.put("form_url", String.format("%s/%s", request.getContext(), request.getPath()));
			
			Map<String, String[]> searchParams = new HashMap<String, String[]>(request.getParameterMap());
			if (searchParams.containsKey("page")) searchParams.remove("page");
			if (searchParams.containsKey("prompt_data")) searchParams.remove("prompt_data");
			form.bind(request.getParameterMap());
			if (form.isValid()) {
				int page;
				try {
					page = new Integer(request.getParameter("page"));
				} catch (NumberFormatException e) {
					page = 1;
				}
				
				Manager<?> manager = managers.forClass(clazz);
				QuerySet<?> qs = manager.filter(form.getCleanedData());
				if (resultColumns != null && resultColumns.length > 0) {
					qs = qs.orderBy(resultColumns);
				}

				Paginator<?> paginator = new Paginator(qs,15);
				
				Page<?> objects = paginator.getPage(page);

				context.put("objects", objects);
				context.put("query_string", asQueryString(searchParams));
				return renderToResponse("javango/contrib/jquery/prompt.ftl", context);
			}
		} else {
			context.put("form_url", String.format("%s/%ssearch/", request.getContext(), request.getPath()));
//			context.put("form_url", String.format("./%ssearch/", request.getPath()));
		}


		context.put("form", form);
		return renderToResponse("javango/contrib/jquery/prompt.ftl", context);		
	}
}
